﻿using System;
using System.Collections.Generic;
using System.Linq;
using Roslyn.Compilers.CSharp;

namespace BITS.PreCompiler.CSharp.Syntax
{
    /// <summary>
    /// Represents a method abstraction.
    /// </summary>
    /// <example>
    /// new Method("MyMethod");
    /// <code>
    /// private void MyMethod() {}
    /// </code>
    /// </example>
    public partial class Operator : BaseMethodMember<Operator, OperatorDeclarationSyntax>
    {
        # region • Fields & Properties •

        /// <summary>
        /// Holds the return type.
        /// </summary>
        private TypeSyntax returnType = Roslyn.Compilers.CSharp.Syntax.ParseTypeName("void");

        /// <summary>
        /// Gets the type of the return.
        /// </summary>
        /// <value>
        /// The type of the return.
        /// </value>
        public TypeSyntax ReturnType
        {
            get { return this.returnType; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance has return type.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has return type; otherwise, <c>false</c>.
        /// </value>
        public Boolean HasReturnType
        {
            get
            {
                return this.ReturnType.ToString()
                    != Roslyn.Compilers.CSharp.Syntax.ParseTypeName("void").ToString();
            }
        }

        /// <summary>
        /// Gets the name of the return type.
        /// </summary>
        /// <value>
        /// The name of the return type.
        /// </value>
        public String ReturnTypeName
        {
            get { return this.returnType.ToString(); }
        }

        /// <summary>
        /// Holds the operator type.
        /// </summary>
        private SyntaxToken operatorType = Roslyn.Compilers.CSharp.Syntax.Token(SyntaxKind.None);

        /// <summary>
        /// Gets or sets the opertaor type.
        /// </summary>
        /// <value>
        /// The type of the operator.
        /// </value>
        public SyntaxToken OperatorType
        {
            get { return this.operatorType; }
        }

        /// <summary>
        /// Holds the parameters
        /// </summary>
        private readonly IList<ParameterSyntax> parameters =
            new List<ParameterSyntax>();

        /// <summary>
        /// Gets the parameters.
        /// </summary>
        public virtual IEnumerable<ParameterSyntax> Parameters
        {
            get { return this.parameters; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance has parameters.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has parameters; otherwise, <c>false</c>.
        /// </value>
        public Boolean HasParameters
        {
            get { return this.Parameters.Any(); }
        }

        /// <summary>
        /// Gets the semicolon token.
        /// </summary>
        private SyntaxToken SemicolonToken
        {
            get
            {
                if (this.IsAbstract || this.Partial)
                    return Roslyn.Compilers.CSharp.Syntax.Token(SyntaxKind.SemicolonToken);

                return default(SyntaxToken);
            }
        }

        /// <summary>
        /// Holds the statements.
        /// </summary>
        private readonly List<StatementSyntax> statements =
            new List<StatementSyntax>();

        /// <summary>
        /// Gets the statements.
        /// </summary>
        public IEnumerable<StatementSyntax> Statements
        {
            get { return this.statements; }
        }

        /// <summary>
        /// Gets the parameter list.
        /// </summary>
        public ParameterListSyntax ParameterList
        {
            get
            {
                if (!this.Parameters.Any())
                    return Roslyn.Compilers.CSharp.Syntax.ParameterList();

                if (this.Parameters.Count() == 1)
                {
                    return Roslyn.Compilers.CSharp.Syntax.ParameterList(
                        parameters: Roslyn.Compilers.CSharp.Syntax.SeparatedList(
                            nodes: this.Parameters,
                            separators: new List<SyntaxToken>()));
                }

                return Roslyn.Compilers.CSharp.Syntax.ParameterList(
                    parameters: Roslyn.Compilers.CSharp.Syntax.SeparatedList(
                        nodes: this.Parameters,
                        separators: Enumerable.Repeat(Roslyn.Compilers.CSharp.Syntax.Token(
                            SyntaxKind.CommaToken), this.Parameters.Count() - 1)));
            }
        }

        /// <summary>
        /// Gets the parameter names.
        /// </summary>
        public IEnumerable<String> ParameterNames
        {
            get
            {
                return this.ParameterList.Parameters
                    .Select(x => x.Identifier.ToString());
            }
        }

        /// <summary>
        /// Gets the parameter names.
        /// </summary>
        public String ParameterNamesString
        {
            get
            {
                return String.Join(", ", this.ParameterList.Parameters
                    .Select(x => x.Identifier.ToString()));
            }
        }

        /// <summary>
        /// Gets the parameter types.
        /// </summary>
        public IEnumerable<String> ParameterTypes
        {
            get
            {
                return this.ParameterList.Parameters
                    .Select(x => x.Type.ToString());
            }
        }

        /// <summary>
        /// Gets the block syntax.
        /// </summary>
        private BlockSyntax BlockSyntax
        {
            get
            {
                if (this.IsAbstract || this.Partial)
                    return null;

                return Roslyn.Compilers.CSharp.Syntax.Block(
                    statements: Roslyn.Compilers.CSharp.Syntax.List(this.Statements));
            }
        }

        # endregion Fields & Properties

        # region • Ctor's •

        /// <summary>
        /// Initializes a new instance of the <see cref="Operator"/> class.
        /// </summary>
        /// <param name="parent">The parent.</param>
        public Operator(IBaseSyntaxNode parent) : base(parent) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="Operator"/> class.
        /// </summary>
        /// <param name="syntax">The method declaration syntax.</param>
        /// <param name="parent">The parent.</param>
        /// <param name="isAbstract">if set to <c>true</c> [is abstract].</param>
        public Operator(OperatorDeclarationSyntax syntax, IBaseSyntaxNode parent = null,
            Boolean isAbstract = true)
            : this(parent)
        {
            if (syntax == null)
                return;

            this.modifiers    = syntax.Modifiers.ToList();
            this.returnType   = syntax.ReturnType;
            this.operatorType = syntax.OperatorToken;

            this.isAbstract = syntax.SemicolonToken.Kind
                == SyntaxKind.SemicolonToken && isAbstract;

            this.ExtractIsPartial();

            this.parameters = syntax.ParameterList.Parameters.ToList();

            if (syntax.Body != null)
                this.statements = syntax.Body.Statements.ToList();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Operator"/> class.
        /// </summary>
        /// <param name="identifier">The identifier.</param>
        /// <param name="modifier">The modifier.</param>
        /// <param name="return">Type of the return.</param>
        /// <param name="parameters">The parameters.</param>
        /// <param name="parent">The parent.</param>
        public Operator(String identifier, Modifier modifier = Modifier.PublicStatic,
            String @return = null, String parameters = null,
            IBaseSyntaxNode parent = null) : this(parent)
        {
            if (!String.IsNullOrEmpty(identifier))
                this.Identifier = Roslyn.Compilers.CSharp.Syntax.Identifier(identifier);

            this.modifiers = ModifierHelper.GetModifers(modifier);

            this.ExtractIsPartial();

            if (!String.IsNullOrEmpty(@return))
                this.returnType = Roslyn.Compilers.CSharp.Syntax.ParseTypeName(@return);

            if (!String.IsNullOrEmpty(parameters))
                this.parameters = Roslyn.Compilers.CSharp.Syntax.ParseParameterList(parameters).Parameters.ToList();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Operator"/> class.
        /// </summary>
        /// <param name="return">The type.</param>
        /// <param name="modifier">The modifier.</param>
        /// <param name="identifier">The identifier.</param>
        /// <param name="parent">The parent.</param>
        public Operator(IBaseSyntaxNode<TypeDeclarationSyntax> @return,
            Modifier modifier = Modifier.PublicStatic, String identifier = null,
            IBaseSyntaxNode parent = null) : this(parent)
        {
            this.modifiers = ModifierHelper.GetModifers(modifier);

            this.ExtractIsPartial();

            if (@return != null)
            {
                this.returnType = Roslyn.Compilers.CSharp.Syntax.ParseTypeName(@return.GetSyntaxNode()
                    .Identifier.ValueText);
            }

            if (!String.IsNullOrEmpty(identifier))
                this.Identifier = Roslyn.Compilers.CSharp.Syntax.Identifier(identifier);
        }

        # endregion Ctor's

        # region • Methods •

        private void ExtractIsPartial()
        {
            var result = this.modifiers
                .Where(x => x.Kind == SyntaxKind.PartialKeyword);

            if (result.Any())
                this.IsPartial();
        }

        /// <summary>
        /// Builds the method decleration.
        /// </summary>
        /// <returns></returns>
        private OperatorDeclarationSyntax BuildMethodDecleration()
        {
            return Roslyn.Compilers.CSharp.Syntax.OperatorDeclaration(
                this.AttributeDeclarationList,
                modifiers: this.ModifierList,
                returnType: this.returnType,
                operatorToken: this.OperatorType,
                operatorKeyword: Roslyn.Compilers.CSharp.Syntax.Token(SyntaxKind.OperatorKeyword),
                parameterList: this.ParameterList,
                body: this.BlockSyntax,
                semicolonToken: this.SemicolonToken);
        }

        /// <summary>
        /// Gets the syntax node.
        /// </summary>
        /// <returns></returns>
        public override OperatorDeclarationSyntax GetSyntaxNode()
        {
            return this.BuildMethodDecleration();
        }

        /// <summary>
        /// Adds the specified append line.
        /// </summary>
        /// <param name="code">The code.</param>
        public override BaseSyntaxNode<Operator, OperatorDeclarationSyntax> Add(String code)
        {
            if (String.IsNullOrEmpty(code))
                return this;

            this.statements.AddRange(this.ParseStatements(code)
                .ToList());

            base.Add(code);

            return this;
        }

        /// <summary>
        /// Adds the specified return type.
        /// </summary>
        /// <example>
        /// new Method("MyMethod").WithReturnType("MyClass");
        /// => private MyClass MyMethod() {}
        /// </example>
        /// <param name="return">The @return.</param>
        /// <returns></returns>
        public Operator WithReturnType(String @return)
        {
            if (!String.IsNullOrEmpty(@return))
                this.returnType = Roslyn.Compilers.CSharp.Syntax.ParseTypeName(@return);

            return this;
        }

        /// <summary>
        /// Adds the specified return type.
        /// </summary>
        /// <example>
        /// new Method("MyMethod").WithReturnType(new Class("MyClass"));
        /// => private MyClass MyMethod() {}
        /// </example>
        /// <param name="return">The @return.</param>
        /// <returns></returns>
        public Operator WithReturnType(IType<TypeDeclarationSyntax> @return)
        {
            if (@return == null)
                return this;

            this.returnType = Roslyn.Compilers.CSharp.Syntax.ParseTypeName(
                @return.GetSyntaxNode().Identifier.ValueText);

            return this;
        }

        public Operator AddRefParameter(String type, String name)
        {
            return this.AddParameter(String.Format("ref {0} {1}", type, name));
        }

        public Operator AddOutParameter(String type, String name)
        {
            return this.AddParameter(String.Format("ref {0} {1}", type, name));
        }

        public Operator AddParameter(String type, String name)
        {
            return this.AddParameter(String.Format("{0} {1}", type, name));
        }

        public Operator AddParameter(String type, String name, String @value)
        {
            return this.AddParameter(String.Format("{0} {1}= {2}", type, name, @value));
        }

        /// <summary>
        /// Add the specific parameter.
        /// </summary>
        /// <example>
        /// new Method("MyMethod").AddParameter("String myString");
        /// => private void MyMethod(String myString) {}
        /// </example>
        /// <param name="parameter">The parameter - Must be in the format '[Type] [Identifier]'</param>
        /// <returns></returns>
        public Operator AddParameter(String parameter)
        {
            if (String.IsNullOrEmpty(parameter))
                return this;

            var @params = Roslyn.Compilers.CSharp.Syntax.ParseParameterList(parameter).Parameters;

            foreach (var param in @params)
                this.parameters.Add(param);

            return this;
        }

        /// <summary>
        /// Add the specific parameter.
        /// </summary>
        /// <example>
        /// new Method("MyMethod").AddParameter<![CDATA[<String>]]>("myString");
        /// => private void MyMethod(String myString) {}
        /// </example>
        /// <typeparam name="TType">The type of the type.</typeparam>
        /// <param name="parameterName">The parameter - Must be in the format '[Type] [Identifier]'</param>
        /// <param name="useFullName">if set to <c>true</c> [use full name].</param>
        /// <returns></returns>
        public Operator AddParameter<TType>(String parameterName, Boolean useFullName = false)
        {
            if (String.IsNullOrEmpty(parameterName))
                return this;

            var parameter = String.Format("{0} {1}",
                useFullName ? typeof(TType).FullName : typeof(TType).Name,
                parameterName);

            return this.AddParameter(parameter);
        }

        /// <summary>
        /// Clones this instance.
        /// </summary>
        /// <returns></returns>
        public Operator Clone()
        {
            return new Operator(this.GetSyntaxNode(), this.Parent);
        }

        /// <summary>
        /// Makes the method public.
        /// </summary>
        /// <example>
        /// new Method("MyMethod");
        /// => private void MyMethod() {}
        /// 
        /// new Method("MyMethod").MakePublic();
        /// => public void MyMethod() {}
        /// </example>
        public Operator MakePublic()
        {
            var result = this.modifiers.Where(x =>
                x.Kind == SyntaxKind.PrivateKeyword)
                .ToList();

            if (result.Any())
                this.modifiers.Remove(result.Single());

            this.modifiers.Add(Roslyn.Compilers.CSharp.Syntax.Token(SyntaxKind.PublicKeyword));

            return this;
        }

        /// <summary>
        /// Clears the memebers.
        /// </summary>
        public void ClearStatements()
        {
            this.statements.Clear();
        }

        # endregion Methods
    }
}
